#pragma once

#include "PropertyOwner.hpp"
#include "EnumObject.hpp"

CYBERTRON_BEGIN

enum EPropertyVisitorType
{
	EPropertyVisitorType_Loader,
	EPropertyVisitorType_Saver,
};

class PropertyVisitor
{
public:
	PropertyVisitor()
	{
		mType = EPropertyVisitorType_Loader;
	}
	virtual ~PropertyVisitor()
	{
	}

	EPropertyVisitorType getType() const
	{
		return mType;
	}

	virtual bool visitProperty(const std::string& propertyName, bool& val) = 0;
	virtual bool visitProperty(const std::string& propertyName, int& val) = 0;
	virtual bool visitProperty(const std::string& propertyName, unsigned int& val) = 0;
	virtual bool visitProperty(const std::string& propertyName, double& val) = 0;
	virtual bool visitProperty(const std::string& propertyName, std::string& val) = 0;

	virtual std::string getData() = 0;
	virtual void setData(const std::string& val) = 0;

	virtual size_t getChildSize(const std::string& propertyName) = 0;
	virtual PropertyVisitor* getChild(const std::string& propertyName, size_t i) = 0;
	virtual PropertyVisitor* addChild(const std::string& propertyName) = 0;

	template <typename EnumType, typename EnumTypeTraits>
	bool visitProperty(const std::string& propName, EnumObject<EnumType, EnumTypeTraits>& val);
	template <typename T>
	bool visitProperty(const std::string& propertyName, T& val);
	template <typename T> 
	bool visitProperty(const std::string& propertyName, std::vector<T>& val);
	template <typename T>
	bool visitProperty(const std::string& propertyName, std::unique_ptr<T>& val);

protected:
	EPropertyVisitorType mType;

private:
	PropertyVisitor(const PropertyVisitor&);
	PropertyVisitor& operator = (const PropertyVisitor&);
};

template <typename EnumType, typename EnumTypeTraits>
bool PropertyVisitor::visitProperty(const std::string& propName, EnumObject<EnumType, EnumTypeTraits>& val)
{
	if (mType == EPropertyVisitorType_Loader)
	{
		std::string strVal;
		if (!visitProperty(propName, strVal))
		{
			return false;
		}

		val.fromString(strVal);
		return true;
	}
	else if (mType == EPropertyVisitorType_Saver)
	{
		std::string strVal = val.toString();
		if (!visitProperty(propName, strVal))
		{
			return false;
		}

		return true;
	}
	else
	{
		throw std::runtime_error("Not Implemented!");
	}
}

template <typename T>
bool PropertyVisitor::visitProperty(const std::string& propertyName, T& val)
{
	if (mType == EPropertyVisitorType_Loader)
	{
		size_t size = getChildSize(propertyName);
		if (size == 0)
		{
			return false;
		}
		PropertyVisitor* pChild = getChild(propertyName, 0);
		if (pChild == nullptr)
		{
			return false;
		}
		if (!val.visit(pChild))
		{
			return false;
		}

		return true;
	}
	else if (mType == EPropertyVisitorType_Saver)
	{
		PropertyVisitor* pChild = addChild(propertyName);
		if (pChild == nullptr)
		{
			return false;
		}
		if (!val.visit(pChild))
		{
			return false;
		}

		return true;
	}
	else
	{
		throw std::runtime_error("Not Implemented!");
	}
}

template <typename T>
bool PropertyVisitor::visitProperty(const std::string& propertyName, std::unique_ptr<T>& val)
{
	if (mType == EPropertyVisitorType_Loader)
	{
		size_t size = getChildSize(propertyName);
		if (size == 0)
		{
			val.reset();
			return true;
		}

		val.reset(new T());
		return visitProperty(propertyName, *val);
	}
	else if (mType == EPropertyVisitorType_Saver)
	{
		if (val == nullptr)
		{
			return true;
		}
		return visitProperty(propertyName, *val);
	}
	else
	{
		throw std::runtime_error("Not Implemented!");
	}
}

template <typename T>
bool PropertyVisitor::visitProperty(const std::string& propertyName, std::vector<T>& val)
{
	if (mType == EPropertyVisitorType_Loader)
	{
		size_t size = getChildSize(propertyName);
		val.resize(size);
		for (size_t i = 0; i < size; ++i)
		{
			PropertyVisitor* pChild = getChild(propertyName, i);
			if (pChild == nullptr)
			{
				return false;
			}
			if (!val[i].visit(pChild))
			{
				return false;
			}
		}

		return true;
	}
	else if (mType == EPropertyVisitorType_Saver)
	{
		for (size_t i = 0; i < val.size(); ++i)
		{
			PropertyVisitor* pChild = addChild(propertyName);
			if (pChild == nullptr)
			{
				return false;
			}
			if (!val[i].visit(pChild))
			{
				return false;
			}
		}

		return true;
	}
	else
	{
		throw std::runtime_error("Not Implemented!");
	}
}

CYBERTRON_END
